package com.lemon.core.conf;


import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.lang.StringUtils;
import org.apache.zookeeper.KeeperException;

import java.io.ByteArrayInputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * <p>本类为单例，一旦初始化就没法改变，如果你需要指定自己的配置路径，在使用本类之前先调用BaseConfig.setConfigFrom()</p>
 * <p>异常code 类似HTTP，4XX系列是业务导致的，例如路径、格式不对；5XX是系统的问题，例如网络问题</p>
 * <p>watch的功能是带监控的，一旦改变节点的值，就能捕捉到。使用的时候还是需要知道配置路径
 * <p>本类的方法<b><i> 只能</i> </b>watch .xml或者.properties文件，如果不想用本类提供的接口， 可以自己写：</p>
 * ZKClient.get().watchStrValueNode( watcher )
 * watcher需要自己实现一个 new StringValueWatcher() 内部类
 * <p>如果配置参数是永远不变，也可以不用watch直接获取节点的字符串内容:</p>
 * ZKClient.get().getStringData( node )
 *
 * @author guoning.liang@opi-corp.com
 */
public class ConfigOnZk {

    private final static String ZKCONFIGS_CHROOT = BaseConfig.getValue("zk.config.prefix", "/mlwconf");

    //    static final Logger log = LoggerFactory.getLogger(ConfigOnZk.class);
    private Map<String, Configuration> totalConfigs = new ConcurrentHashMap<String, Configuration>();

    private static ConfigOnZk instance = null;

    private ConfigOnZk() {

    }

    /**
     * <p>本类为单例，一旦调用就没法改变，如果你需要指定自己的zk配置路径，在使用本类之前先调用BaseConfig.setConfigFrom()</p>
     *
     * @return
     */
    public static ConfigOnZk getInstance() {
        if (instance == null) {
            synchronized (ConfigOnZk.class) {
                if (instance == null) {
                    instance = new ConfigOnZk();
                }

            }
        }
        return instance;
    }

    /**
     * @param pathFromMlwconf zk上的配置文件路径，从mlwconf下开始，也就是不带/mlwconf，必须是.xml或者.properties结尾
     * @param content         pathFromMlwconf节点中的内容
     * @param configType      配置文件后缀
     */
    private void loadConfig(final String pathFromMlwconf, String content, String configType) {
        if (configType.equals(".xml")) {
            Configuration conf = new XMLConfiguration();
            try {
                ((XMLConfiguration) conf).load(new ByteArrayInputStream(content.getBytes()), "UTF-8");
            } catch (ConfigurationException e) {
//                log.error("parse config : " + pathFromMlwconf + " exception!", e);
            }
            totalConfigs.put(pathFromMlwconf, conf);
            ZKClient.get().watchStrValueNode(pathFromMlwconf,
                    new ZKClient.StringValueWatcher() {
                        public void valueChanged(String l) {
                            Configuration conf = new XMLConfiguration();
                            try {
                                ((XMLConfiguration) conf).load(new ByteArrayInputStream(l.getBytes()), "UTF-8");
                            } catch (ConfigurationException e) {
//                                log.error("parse config : " + pathFromMlwconf + " exception!", e);
                            }
                            totalConfigs.put(pathFromMlwconf, conf);
                        }
                    });
        } else if (configType.endsWith(".properties")) {
            Configuration conf = new PropertiesConfiguration();
            try {
                ((PropertiesConfiguration) conf).load(new ByteArrayInputStream(content.getBytes()), "UTF-8");
            } catch (ConfigurationException e) {
//                log.error("parse config : " + pathFromMlwconf + " exception!", e);
            }
            totalConfigs.put(pathFromMlwconf, conf);
            ZKClient.get().watchStrValueNode(pathFromMlwconf,
                    new ZKClient.StringValueWatcher() {
                        public void valueChanged(String l) {
                            Configuration conf = new PropertiesConfiguration();
                            try {
                                ((PropertiesConfiguration) conf).load(new ByteArrayInputStream(l.getBytes()), "UTF-8");
                            } catch (ConfigurationException e) {
//                                log.error("parse config : " + pathFromMlwconf + " exception!", e);

                            }
                            totalConfigs.put(pathFromMlwconf, conf);
                        }
                    });
        }

    }

    /**
     * 注册监控一个节点，并拿回节点的配置信息
     *
     * @param pathFromMlwconf, 从mlwconf下开始，也就是不带/mlwconf，必须是.xml或者.properties结尾
     * @return
     */
    private Configuration getConfigAndWatch(String pathFromMlwconf) {
        if (pathFromMlwconf.startsWith("/+")) {
            pathFromMlwconf = pathFromMlwconf.replaceFirst("/+", "/");
        }

        if (!pathFromMlwconf.startsWith("/")) {
            pathFromMlwconf = ZKCONFIGS_CHROOT + "/" + pathFromMlwconf;
        }

        try {
            String content = ZKClient.get().getStringData(pathFromMlwconf);
            if (content == null) {
                throw new RuntimeException("404 path of :" + pathFromMlwconf + " not found");
            } else {
                int lastDotIndex = pathFromMlwconf.lastIndexOf(".");
                final String configType = pathFromMlwconf.substring(lastDotIndex);
                if (totalConfigs.containsKey(pathFromMlwconf)) {
                    return totalConfigs.get(pathFromMlwconf);
                } else {
                    this.loadConfig(pathFromMlwconf, content, configType);
                    return totalConfigs.get(pathFromMlwconf);
                }
            }
        } catch (KeeperException e) {
//            log.error(" visit zookeeper KeeperException!", e);
            throw new RuntimeException(" visit zookeeper KeeperException!", e);
        } catch (InterruptedException e) {
//            log.error(" visit zookeeper InterruptedException!", e);
            throw new RuntimeException(" visit zookeeper InterruptedException!");
        }
    }

    /**
     * 注册监控一个节点，并拿回节点的配置信息, 直接取配置里的Key value
     *
     * @param pathFromMlwconf, 从mlwconf下开始，也就是不带/mlwconf，必须是.xml或者.properties结尾
     * @param key              property文件中某键值对
     * @return
     */
    public String getValue(String pathFromMlwconf, String key) {
        Configuration conf = getConfigAndWatch(pathFromMlwconf);
        String value = null;
        String[] values = conf.getStringArray(key);
        if (values != null && values.length > 1) {
            value = StringUtils.join(values, ",");
        } else {
            value = conf.getString(key);
        }
        return value;
    }

    /**
     * 注册监控一个节点，并拿回节点的配置信息, 直接取配置里的Key value 默认值
     *
     * @param pathFromMlwconf, 从mlwconf下开始，也就是不带/mlwconf，必须是.xml或者.properties结尾
     * @param key
     * @return
     */
    public String getValue(String pathFromMlwconf, String key, String defaultValue) {
        String value = getValue(pathFromMlwconf, key);

        return StringUtils.isBlank(value) ? defaultValue : value;
    }

    /**
     * @param pathFromMlwconf
     * @param key
     * @return 如果properties文件中某个value是以逗号隔开的多个值，则可以使用该方法返回一个数组
     */
    public String[] getArrayValue(String pathFromMlwconf, String key) {
        Configuration conf = getConfigAndWatch(pathFromMlwconf);
        return conf.getStringArray(key);
    }

    /**
     * 注册监控一个节点，并拿回节点的配置信息, 直接取配置里的Key value 默认值
     *
     * @param pathFromMlwconf, 从mlwconf下开始，也就是不带/mlwconf，必须是.xml或者.properties结尾
     * @return
     */
    @SuppressWarnings("rawtypes")
    public int getElemSize(String pathFromMlwconf, String selector) {
        Configuration config = getConfigAndWatch(pathFromMlwconf);
        Object element = config.getProperty(selector);
        if (element == null) {
            return 0;
        }

        if (element instanceof Collection) {
            Collection collec = (Collection) config.getProperty(selector);
            return collec.size();
        }

        return 1;
    }


    /**
     * 获取节点所有配置的key
     *
     * @param pathFromMlwconf
     * @return
     */
    public Iterator<String> getKeys(String pathFromMlwconf) {
        Configuration config = getConfigAndWatch(pathFromMlwconf);
        return config.getKeys();
    }
}
